#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File Name: Halo2Uploader.py
# Author: bobo
# Created Time: 202411091200
# pip install python-slugify

import os
import traceback

import requests
from slugify import slugify

from .MarkdownRender import render_markdown
from .MetaData import get_md_metadata

# 如果是在halo2控制台创建的文章，["spec"]["slug"]和["metadata"]["name"]可能不同，需要加以区分。
""" markdown文件元数据样例
title:xx
date:2024-01-01T00:00:00
tags:
- xx
- xx
categories:
- xx
cover: xx
abbrlink: xx
published: True
"""
"""站点链接，不带尾随`/`斜杠"""
BASE_URL = os.environ.get("HALO_SITE")
""" 用户API个人令牌，在个人中心中的个人令牌里面生成 """
API_KEY = os.environ.get("HALO_TOKEN")
"""基础请求头"""
BASE_HEADER = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
SUCCESS = 0
FAILED = 400

# api接口链接
# 获取所有文章的列表
POSTS_LIST_URL = "/apis/api.console.halo.run/v1alpha1/posts"
# 获取单个文件信息，name为metadata中的name，"/apis/content.halo.run/v1alpha1/posts/{name}"
POSTS_INFO_URL = "/apis/content.halo.run/v1alpha1/posts/{name}"


def handle_response(respose, return_value=None):
    """
    解析返回报文，status=400代表返回错误，通过detail获取错误信息

    """
    # print(respose)
    if respose["status"] == FAILED:
        return FAILED, respose["detail"]
        # raise Exception(respose["detail"])
    else:
        return SUCCESS, respose if not return_value else return_value


def get_page_list():
    """获取所有文章的列表
    返回：{tile:[metadata name, slug]}, raw原始查询数据
    """
    ret = requests.get(
        url=BASE_URL + POSTS_LIST_URL, headers=BASE_HEADER
    )  # 这里要用json来传data
    # print(ret.text)
    posts_raw = ret.json()
    post_dict = {}
    for posts in posts_raw["items"]:
        post = posts["post"]
        title = post["spec"]["title"]
        slug = post["spec"]["slug"]
        meta_name = post["metadata"]["name"]
        if not post["spec"]["deleted"]:
            post_dict[title] = [meta_name, slug]

    return post_dict, posts_raw
    # return handle_response(posts_raw, (post_dict, posts_raw))


def get_posts_info(name: str):
    """
    获取单个文章的信息
    - name: 文章详细信息中的["metadata"]["name"]
    - 注：可以通过所有文章列表API找到对应文章的该字段
    """
    ret = requests.get(
        url=BASE_URL + POSTS_INFO_URL.replace("{name}", name),
        headers=BASE_HEADER,
    )
    # print(ret.text)
    return ret.json()
    # return handle_response(ret.json())


# 获取单个文章的内容
POSTS_CONTENT_URL = "/apis/api.console.halo.run/v1alpha1/posts/{name}/head-content"


def get_posts_content(name: str):
    """
    获取单个文章的内容
    - name: 文章详细信息中的["metadata"]["name"]
    - 注：可以通过所有文章列表API找到对应文章的该字段
    """
    ret = requests.get(
        url=BASE_URL + POSTS_CONTENT_URL.replace("{name}", name),
        headers=BASE_HEADER,
    )
    return ret.json()
    # return handle_response(ret.json())


TAGS_LIST_URL = "/apis/content.halo.run/v1alpha1/tags"


def get_tags_dict(name_list: list = None):
    """
    获取所有tag的列表
    - name_list: 获取指定tag的信息
    return: {tag_name:metadata_name},[tag list not exist]
    """
    ret = requests.get(url=BASE_URL + TAGS_LIST_URL, headers=BASE_HEADER)
    tags_raw = ret.json()
    tags = tags_raw["items"]
    all_tag_dict = {}
    for tag in tags:
        dispaly_name = tag["spec"]["displayName"]
        meta_name = tag["metadata"]["name"]
        all_tag_dict[dispaly_name] = meta_name

    if name_list is None:  # 没有指定就返回所有
        return all_tag_dict, []
    else:
        tag_dict, tag_not_exist = {}, []
        for tag_name in name_list:
            if tag_name in all_tag_dict:
                tag_dict[tag_name] = all_tag_dict[tag_name]
            else:  # 不存在，新建
                # print(f"标签'{tag_name}'不存在...")
                tag_not_exist.append(tag_name)
        return tag_dict, tag_not_exist


CATEGORY_LIST_URL = "/apis/content.halo.run/v1alpha1/categories"


def get_categories_dict(name_list: list = None):
    """
    获取所有categories的列表
    - name_list: 获取指定category的信息
    return: {tag_name:metadata_name}
    """
    ret = requests.get(
        url=BASE_URL + CATEGORY_LIST_URL,
        headers=BASE_HEADER,
    )
    categories_raw = ret.json()
    categories = categories_raw["items"]
    all_cat_dict = {}
    for cat in categories:
        dispaly_name = cat["spec"]["displayName"]
        meta_name = cat["metadata"]["name"]
        all_cat_dict[dispaly_name] = meta_name

    if name_list is None:  # 没有指定就返回所有
        return all_cat_dict, []
    else:
        cat_dict, cat_not_exist = {}, []
        for name in name_list:
            if name in all_cat_dict:
                cat_dict[name] = all_cat_dict[name]
            else:  # 不存在，新建
                # print(f"分类'{tag_name}'不存在，新建中...")
                cat_not_exist.append(name)
        return cat_dict, cat_not_exist


POSTS_UPDATE_INFO_URL = "/apis/content.halo.run/v1alpha1/posts/{name}"
POSTS_UPDATE_CONTENT_URL = "/apis/api.console.halo.run/v1alpha1/posts/{name}/content"


def update_page(
    name: str,
    raw_content: str = "",
    title: str = "",
    cover: str = "",
    categories: list[str] = [],
    tags: list[str] = [],
    publish_time=None,
):
    """
    如果该文章已有，则调用update方法，name必须是已有文章的永久name。

    除了name外的参数可以为空，为空代表不更新。

    - name: metadata中的name #文章永久链接（abbrlink）
    - raw_content：原始md文件
    - title: 文章标题
    - cover: 文章封面链接
    - categories: 文章分类（必须先创建分类并用返回值的["metadata"]["name"]来请求）
    - tags: 文章tag（必须先创建tags并用返回值的["metadata"]["name"]来请求）
    - publish_time: 更新时间
    """
    data = {}
    data["post"] = get_posts_info(name)
    data["content"] = get_posts_content(name)
    # print(data["content"])
    # 修改文章内容
    if raw_content != "":
        data["content"]["raw"] = raw_content
        data["content"]["content"] = render_markdown(raw_content)
    # 修改文章信息
    if title != "":
        data["post"]["spec"]["title"] = title
    if cover != "":
        data["post"]["spec"]["cover"] = cover
    if categories != []:
        data["post"]["spec"]["categories"] = categories
    if tags != []:
        data["post"]["spec"]["tags"] = tags
    if publish_time:
        data["post"]["spec"]["publishTime"] = publish_time

    # 测试
    # print(data["post"])
    # return 0

    # print(json.dumps(data, ensure_ascii=False), '\n')
    # 更新文章信息
    ret1 = requests.put(
        url=BASE_URL + POSTS_UPDATE_INFO_URL.replace("{name}", name),
        json=data["post"],
        headers=BASE_HEADER,
    )  # 这里要用json来传data
    return_code1, response1 = handle_response(ret1.json())
    if return_code1 == FAILED:
        return return_code1, response1
    # 更新文章内容
    ret2 = requests.put(
        url=BASE_URL + POSTS_UPDATE_CONTENT_URL.replace("{name}", name),
        json=data["content"],
        headers=BASE_HEADER,
    )  # 这里要用json来传data
    # return (ret1.json(), ret2.json())
    return handle_response(ret2.json())


def post_page(
    raw_content: str,
    title: str,
    slug: str,
    cover: str,
    categories: list[str],
    tags: list[str],
    pinned: bool = False,
    allow_comment: bool = True,
    is_public: bool = True,
    publish_time=None,
):
    """
    上传文章，使用本函数前需要在halo2中安装任意markdown编辑器插件

    - raw_content：原始md文件
    - title: 文章标题
    - slug: 文章永久链接（abbrlink）
    - cover: 文章封面链接
    - categories: 文章分类（必须先创建分类并用返回值的["metadata"]["name"]来请求）
    - tags: 文章tag（必须先创建tags并用返回值的["metadata"]["name"]来请求）
    - pinned: 是否置顶
    - allow_comment: 是否允许评论
    - is_public: 是否公开
    - publish_time: 更新时间（暂时没有找到调用方式）
    """
    try:
        data = {
            "post": {
                "spec": {
                    "title": title,
                    "slug": slug,
                    "template": "",
                    "cover": cover,
                    "deleted": False,
                    "publish": False,
                    "publishTime": publish_time,
                    "pinned": pinned,
                    "allowComment": allow_comment,
                    "visible": "PUBLIC" if is_public else "PRIVATE",
                    "priority": 0,
                    "excerpt": {"autoGenerate": True, "raw": ""},
                    "categories": categories,
                    "tags": tags,
                    "htmlMetas": [],
                },
                "apiVersion": "content.halo.run/v1alpha1",
                "kind": "Post",
                "metadata": {"name": slug},
            },
            "content": {
                "raw": raw_content,
                "content": render_markdown(raw_content),
                "rawType": "markdown",
            },
        }
        ret = requests.post(
            url=BASE_URL + POSTS_LIST_URL,
            json=data,
            headers=BASE_HEADER,
        )  # 这里要用json来传data
        # print(ret.text)
        # return (ret.json(), {})
        return handle_response(ret.json())
    except:
        print(traceback.format_exc())
        return FAILED, "post_page异常"


PUBLISH_URL = "/apis/api.console.halo.run/v1alpha1/posts/{name}/publish"


def publish_page(name: str):
    """发布文章，name为medada中的name"""
    ret = requests.put(
        url=BASE_URL + PUBLISH_URL.replace("{name}", name), headers=BASE_HEADER
    )
    # print(ret.text)
    return ret.json()


UNPUBLISH_URL = "/apis/api.console.halo.run/v1alpha1/posts/{name}/unpublish"


def unpublish_page(name: str):
    """取消发布文章，name为medada中的name"""
    ret = requests.put(
        url=BASE_URL + UNPUBLISH_URL.replace("{name}", name), headers=BASE_HEADER
    )
    # print(ret.text)
    return ret.json()


def create_tag(name: str, color: str = "#ffffff", cover: str = ""):
    """
    创建tag
    注意：创建前应该先获取已有tag并判断是否有同名tag（否则会创建多个同名tag）
    - name: tag的名字
    - color: tag的颜色
    - cover: tag的展示封面
    返回新创建tag的metadata的name
    """
    current_tags_dict, not_exist = get_tags_dict()
    if name in current_tags_dict:  # 如果有同名tag
        return 0

    data = {
        "spec": {
            "displayName": name,
            "slug": slugify(name),
            "color": color,
            "cover": cover,
        },
        "apiVersion": "content.halo.run/v1alpha1",
        "kind": "Tag",
        "metadata": {"name": "", "generateName": "tag-", "annotations": {}},
    }
    # print(data)
    print(f"标签'{name}'不存在，创建中...")
    ret = requests.post(url=BASE_URL + TAGS_LIST_URL, headers=BASE_HEADER, json=data)
    # 返回报文中没有status，不能用handle_response解析
    respose = ret.json()
    # code, respose = handle_response(respose)
    return respose["metadata"]["name"]


def create_category(
    name: str, color: str = "#ffffff", cover: str = "", description: str = ""
):
    """
    创建分组

    注意：创建前应该先获取已有分组并判断是否有同名分组（否则会创建多个同名分组）

    - name: category的名字
    - color: category的颜色
    - cover: category的展示封面
    - description: category的描述
    """
    data = {
        "spec": {
            "displayName": name,
            "slug": slugify(name),
            "color": color,
            "cover": cover,
            "description": description,
            "template": "",
            "priority": len(name),
            "children": [],
        },
        "apiVersion": "content.halo.run/v1alpha1",
        "kind": "Category",
        "metadata": {"name": "", "generateName": "category-", "annotations": {}},
    }
    print(f"分类'{name}'不存在，创建中...")
    ret = requests.post(
        url=BASE_URL + CATEGORY_LIST_URL, headers=BASE_HEADER, json=data
    )
    respose = ret.json()
    # respose = handle_response(ret.json())
    return respose["metadata"]["name"]


def upload_md_file(md_file: str):
    """
    同步本地md文件到halo
    - md_file: 文件路径
    """
    # 解析元数据
    print(f"同步markdown文件'{md_file}'")
    md_metadata, content = get_md_metadata(md_file)
    if not md_metadata:
        msg = f"md文件'{md_file}'没有元数据!"
        # print(msg)
        raise Exception(msg)
    # print("post:", md_metadata)

    # 文章创建和最后一次修改时间，如果没有就为None
    create_time = md_metadata["createtime"]
    modify_time = md_metadata["modifytime"]

    # 查询博客文章列表
    posts, raw = get_page_list()
    # 打印文章列表
    # print("total:", raw["total"])
    # print("totalPages:", raw["totalPages"])
    # for post in posts:
    #     meat_name, slug = posts[post]
    #     print("post: ", post, meat_name, slug)

    title = md_metadata["title"]
    if title in posts:  # 文章已经存在，判断是否要更新
        meta_name, slug = posts[title]
        post_info = get_posts_info(meta_name)
        last_modify_time = post_info["status"]["lastModifyTime"]
        # print(post_info)
        from datetime import datetime, timedelta

        td = timedelta(hours=8)
        posts_modify_time = datetime.strptime(
            last_modify_time[:19], "%Y-%m-%dT%H:%M:%S"
        )
        if modify_time > posts_modify_time + td:  # halo修改时间少8小时，加上8小时
            print(
                f"文章已存在，有更新..., md:{modify_time}, halo:{posts_modify_time+td}"
            )
        else:
            print(
                f"文章已存在，没有更新..., md:{modify_time}, halo:{posts_modify_time+td}"
            )
            return 0  # 文章没有更新，退出

    # 封面图
    if "cover" not in md_metadata:
        md_metadata["cover"] = ""
    # 永久链接
    if "abbrlink" in md_metadata:
        slug = md_metadata["abbrlink"]
    else:
        slug = slugify(md_metadata["title"])

    # 上传时间解析，"publishTime": "2024-01-22T05:12:33.716773059Z"
    publish_time_str = None
    if "date" in md_metadata:
        # 9位，不够的后补0
        microseconds_str = f"{md_metadata['date'].microsecond:0<9}"
        publish_time_str = (
            f"{md_metadata['date'].strftime('%Y-%m-%dT%H:%M:%S')}.{microseconds_str}Z"
        )
        print("[publish_time]", publish_time_str)

    tag_list, category_list = [], []
    if "tags" in md_metadata:
        # 标签，没有的话就新建
        tag_dict, tag_not_exist = get_tags_dict(md_metadata["tags"])
        tag_list = list(
            tag_dict.values()
        )  # 取metadata的name，增加或修改文件使用这个标识name
        for tag in tag_not_exist:
            new_tag_metadata_name = create_tag(tag)
            tag_list.append(new_tag_metadata_name)
        # print("tag list:", tag_list)
    if "categories" in md_metadata:
        # 分类，没有的话就新建
        cat_dict, cat_not_exist = get_categories_dict(md_metadata["categories"])
        # 取metadata的name，增加或修改文件使用这个标识name
        category_list = list(cat_dict.values())
        for cat in cat_not_exist:
            new_cat_metadata_name = create_category(cat)
            category_list.append(new_cat_metadata_name)

    if title in posts:  # 文章已经存在，更新
        print("文章已存在")
        meta_name, slug = posts[title]
        # print(meta_name, slug)
        code, response = update_page(
            meta_name,
            content,
            title,
            md_metadata["cover"],
            category_list,
            tag_list,
            publish_time=publish_time_str,
        )
        if code == FAILED:
            print(f"更新文章'{title}'失败:{response}.")
        print(f"更新文章'{title}'成功.")
    else:  # 文章不存在，新增
        print("文章不存在")
        code, response = post_page(
            content,
            title,
            slug,
            md_metadata["cover"],
            category_list,
            tag_list,
            pinned=False,
            publish_time=publish_time_str,
        )
        if code == FAILED:
            print(f"增加文章失败，错误：{response}")
        else:
            meta_name = response["metadata"]["name"]
            print(f"增加文章成功：{title}")

    if "published" in md_metadata and md_metadata["published"]:
        print("发布文章")
        publish_page(meta_name)
    else:
        print("取消发布文章")
        unpublish_page(meta_name)
